/**
 * 
 */
package org.vsg.common.ibatis3;

import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.URL;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Vector;

import javax.sql.DataSource;

import org.apache.ibatis.builder.xml.XMLMapperBuilder;
import org.apache.ibatis.mapping.Environment;
import org.apache.ibatis.parsing.XNode;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.apache.ibatis.transaction.managed.ManagedTransactionFactory;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy;
import org.springframework.util.Assert;

/**
 * @author Bill vison
 *
 */
public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>,
		InitializingBean {
	
	/**
	 * set the logger for develope code , do not use System.print(ln) to out print message.
	 */
	protected static final Logger logger = LoggerFactory.getLogger(SqlSessionFactoryBean.class);	
	
	/**
	 * set the ibatis3 resource config location
	 */
	private Resource configLocation;
	
	/**
	 * define domain file of mapper 
	 */
	private String mapperDomainFile;
	
	private Resource[] mapperLocations;   
    
	private DataSource dataSource;
    
    private boolean useTransactionAwareDataSource = true;
    
    private SqlSessionFactory sqlSessionFactory;
	

	/* (non-Javadoc)
	 * @see org.springframework.beans.factory.FactoryBean#getObject()
	 */
	@Override
	public SqlSessionFactory getObject() throws Exception {
		// TODO Auto-generated method stub
		return sqlSessionFactory;
	}

	private SqlSessionFactory createSqlSessionFactory() throws IOException {   
        Reader reader = new InputStreamReader(getConfigLocation().getInputStream());   
        try {   
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);   
            Configuration conf = sqlSessionFactory.getConfiguration();   
            if(dataSource != null) {   
                DataSource dataSourceToUse = this.dataSource;    
                if (this.useTransactionAwareDataSource  && !(this.dataSource instanceof TransactionAwareDataSourceProxy)) {     
                    dataSourceToUse = new TransactionAwareDataSourceProxy(this.dataSource);     
                }   
                   
                conf.setEnvironment(new Environment("development",new ManagedTransactionFactory(),dataSourceToUse));   
                sqlSessionFactory = new SqlSessionFactoryBuilder().build(conf);   
            }   
               
            if(mapperLocations != null) {   
                Map<String, XNode> sqlFragments = new HashMap<String, XNode>();   
                for(Resource r : mapperLocations) {   
                    logger.debug("Loading iBatis3 mapper xml from file["+r.getFile().getAbsolutePath()+"]");   
                       
                    Reader mapperReader = new InputStreamReader(r.getInputStream());   
                    try {   
                        XMLMapperBuilder mapperBuilder = new XMLMapperBuilder(mapperReader,conf,r.getFile().getAbsolutePath(),sqlFragments);   
                        mapperBuilder.parse();   
                    }finally {   
                        mapperReader.close();   
                    }   
                }   
            }
            
            // ---- extend mapper file handle ---
            if (mapperDomainFile != null) {
            	// --- load file ---
        		ClassLoader clsLoader = this.getClass().getClassLoader();
        		Enumeration<URL> enume = clsLoader.getResources(mapperDomainFile);
        		URL url = null;
        		
        		Collection<String> mapperXml = new Vector<String>();
        		
        		try {
					
        			SAXReader saxreader = new SAXReader();        		
					
        			while (enume.hasMoreElements()) {
						url = enume.nextElement();
						Document doc = saxreader.read(url);
						parseMapper(doc , mapperXml);
					}
        			
        			// ---- parse to resource object ---
                    Map<String, XNode> sqlFragments = new HashMap<String, XNode>();   
                    String mapperFile = null;
                    for (Iterator<String> mapperIter = mapperXml.iterator() ; mapperIter.hasNext();) {
                    	mapperFile = mapperIter.next();
                    	Resource r = new ClassPathResource(mapperFile);
                    	
                    	logger.debug("Loading iBatis3 mapper xml from file["+r.getFile().getAbsolutePath()+"]");   
                        
                        Reader mapperReader = new InputStreamReader(r.getInputStream());   
                        try {   
                            XMLMapperBuilder mapperBuilder = new XMLMapperBuilder(mapperReader,conf,r.getFile().getAbsolutePath(),sqlFragments);   
                            mapperBuilder.parse();   
                        }finally {   
                            mapperReader.close();   
                        }                     	
                    }
     			
        			
        			
				} catch (DocumentException e) {
					// TODO Auto-generated catch block
					logger.warn("parse xml file error : " + url);
					e.printStackTrace();
				}        		
            }
            
            return sqlSessionFactory;   
        }finally {   
            reader.close();   
        }   
    } 	

	// --- use dom4j ---
	private void parseMapper(Document doc , Collection<String> mapperXmlVec) {
		Element rootElement = doc.getRootElement();
		
		List<Element> eles = rootElement.elements("value");

		Element elem = null;
		for (Iterator<Element> elemsIter = eles.iterator() ; elemsIter.hasNext() ; ) {
			elem = elemsIter.next();
			
			if (mapperXmlVec.contains( elem .getText() )) {
				logger.warn("mapper xml file ['"+ elem.getText() +"'] existed loader !");
			} else {
				mapperXmlVec.add( elem.getText() );
			}
		}


	}
	
	
	/* (non-Javadoc)
	 * @see org.springframework.beans.factory.FactoryBean#getObjectType()
	 */
	@Override
	public Class<?> getObjectType() {
		// TODO Auto-generated method stub
		return SqlSessionFactory.class;
	}

	/* (non-Javadoc)
	 * @see org.springframework.beans.factory.FactoryBean#isSingleton()
	 */
	@Override
	public boolean isSingleton() {
		// TODO Auto-generated method stub
		return true;
	}

	/* (non-Javadoc)
	 * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
	 */
	@Override
	public void afterPropertiesSet() throws Exception {
		// TODO Auto-generated method stub
		Assert.notNull(configLocation,"configLocation must be not null");   
        
        sqlSessionFactory = createSqlSessionFactory(); 
	}

	public Resource getConfigLocation() {
		return configLocation;
	}

	public void setConfigLocation(Resource configLocation) {
		this.configLocation = configLocation;
	}

	public Resource[] getMapperLocations() {
		return mapperLocations;
	}

	public void setMapperLocations(Resource[] mapperLocations) {
		this.mapperLocations = mapperLocations;
	}

	public boolean isUseTransactionAwareDataSource() {
		return useTransactionAwareDataSource;
	}

	public void setUseTransactionAwareDataSource(
			boolean useTransactionAwareDataSource) {
		this.useTransactionAwareDataSource = useTransactionAwareDataSource;
	}

	public DataSource getDataSource() {
		return dataSource;
	}

	public void setDataSource(DataSource dataSource) {
		this.dataSource = dataSource;
	}

	public String getMapperDomainFile() {
		return mapperDomainFile;
	}

	public void setMapperDomainFile(String mapperDomainFile) {
		this.mapperDomainFile = mapperDomainFile;
	}



	
	
}
